﻿using System;

namespace Grimgine.Math
{
    /// <summary>
    /// 
    /// </summary>
    public struct Vector3 : IEquatable<Vector3>
    {
        #region Fields

        /// <summary>
        /// 
        /// </summary>
        public float X, Y, Z;

        #endregion

        #region Properties

        /// <summary>
        /// 
        /// </summary>
        public static Vector3 Zero
        {
            get
            {
                return
                (
                    new Vector3
                    (
                        0, 0, 0
                    )
                );
            }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// 
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="z"></param>
        public Vector3(float x, float y, float z)
        {
            X = x; Y = y; Z = z;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="v"></param>
        public Vector3(Vector3 v)
        {
            X = v.X; Y = v.Y; Z = v.Z;
        }

        #endregion

        #region Vector Arithmetic

        /// <summary>
        /// 
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static Vector3 operator +(Vector3 v1, Vector3 v2)
        {
            return
            (
                new Vector3
                (
                    v1.X + v2.X,
                    v1.Y + v2.Y,
                    v1.Z + v2.Z
                )
            );
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static Vector3 operator -(Vector3 v1, Vector3 v2)
        {
            return
            (
                new Vector3
                (
                    v1.X - v2.X,
                    v1.Y - v2.Y,
                    v1.Z - v2.Z
                )
            );
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static Vector3 operator *(Vector3 v1, Vector3 v2)
        {
            return
            (
                new Vector3
                (
                    v1.X * v2.X,
                    v1.Y * v2.Y,
                    v1.Z * v2.Z
                )
            );
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static Vector3 operator /(Vector3 v1, Vector3 v2)
        {
            return
            (
                new Vector3
                (
                    v1.X / v2.X,
                    v1.Y / v2.Y,
                    v1.Z / v2.Z
                )
            );
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="s2"></param>
        /// <returns></returns>
        public static Vector3 operator *(Vector3 v1, float s2)
        {
            return
            (
                new Vector3
                (
                    v1.X * s2,
                    v1.Y * s2,
                    v1.Z * s2
                )
            );
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="s2"></param>
        /// <returns></returns>
        public static Vector3 operator /(Vector3 v1, float s2)
        {
            return
            (
                new Vector3
                (
                    v1.X / s2,
                    v1.Y / s2,
                    v1.Z / s2
                )
            );
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="s1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static Vector3 operator *(float s1, Vector3 v2)
        {
            return v2 * s1;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static float Dot(Vector3 v1, Vector3 v2)
        {
            return v1.X * v2.X + v1.Y * v2.Y + v1.Z * v2.Z;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="v"></param>
        /// <returns></returns>
        public float Dot(Vector3 v)
        {
            return Dot(this, v);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static Vector3 Cross(Vector3 v1, Vector3 v2)
        {
            return
            (
                new Vector3
                (
                    v1.Y * v2.Z - v1.Z * v2.Y,
                    v1.Z * v2.X - v1.X * v2.Z,
                    v1.X * v2.Y - v1.Y * v2.X
                )
            );
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="v"></param>
        /// <returns></returns>
        public Vector3 Cross(Vector3 v)
        {
            return Cross(this, v);
        }

        #endregion

        #region Reinforcement and Negation

        /// <summary>
        /// 
        /// </summary>
        /// <param name="v1"></param>
        /// <returns></returns>
        public static Vector3 operator +(Vector3 v1)
        {
            return
            (
                new Vector3
                (
                    +v1.X,
                    +v1.Y,
                    +v1.Z
                )
            );
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="v1"></param>
        /// <returns></returns>
        public static Vector3 operator -(Vector3 v1)
        {
            return
            (
                new Vector3
                (
                    - v1.X,
                    - v1.Y,
                    - v1.Z
                )
            );
        }

        #endregion

        #region Vector Comparison

        /// <summary>
        /// 
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static bool operator <(Vector3 v1, Vector3 v2)
        {
            return v1.Length() < v2.Length();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static bool operator <=(Vector3 v1, Vector3 v2)
        {
            return v1.Length() <= v2.Length();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static bool operator >(Vector3 v1, Vector3 v2)
        {
            return v1.Length() > v2.Length();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static bool operator >=(Vector3 v1, Vector3 v2)
        {
            return v1.Length() >= v2.Length();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static bool operator ==(Vector3 v1, Vector3 v2)
        {
            return v1.X == v2.X && v1.Y == v2.Y && v1.Z == v2.Z;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static bool operator !=(Vector3 v1, Vector3 v2)
        {
            return !(v1 == v2);
        }

        #endregion

        #region Vector Length

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public float Length()
        {
            return (float)System.Math.Sqrt(LengthSquared());
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public float LengthSquared()
        {
            return (X * X + Y * Y + Z * Z); 
        }

        #endregion

        #region Vector Normalization

        /// <summary>
        /// 
        /// </summary>
        /// <param name="v"></param>
        /// <returns></returns>
        public static Vector3 Normalize(Vector3 v)
        {
            if (v.Length() == 0)
            {
                return Vector3.Zero;
            }
            else
            {
                float inverse = 1 / v.Length();
                return
                (
                    new Vector3
                    (
                        v.X * inverse,
                        v.Y * inverse,
                        v.Z * inverse
                    )
                );
            }   
        }

        /// <summary>
        /// 
        /// </summary>
        public void Normalize()
        {
            this = Normalize(this);
        }

        #endregion

        #region Distance Between Two Vectors

        /// <summary>
        /// 
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static float Distance(Vector3 v1, Vector3 v2)
        {
            return
            (
                (float)System.Math.Sqrt
                (
                    (v1.X - v2.X) * (v1.X - v2.X) +
                    (v1.Y - v2.Y) * (v1.Y - v2.Y) +
                    (v1.Z - v2.Z) * (v1.Z - v2.Z)
                )
            );
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="v"></param>
        /// <returns></returns>
        public float Distance(Vector3 v)
        {
            return Distance(this, v);
        }

        #endregion

        #region Vector Methods

        /// <summary>
        /// 
        /// </summary>
        /// <param name="p1"></param>
        /// <param name="p2"></param>
        /// <param name="p3"></param>
        /// <param name="p4"></param>
        /// <param name="t"></param>
        /// <returns></returns>
        public static Vector3 CatmullRom(Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4, float t)
        {
            Vector3 tan1 = (p3 - p1) / 2.0f;
            Vector3 tan2 = (p4 - p2) / 2.0f;

            // powers of t
            float t2 = t * t;
            float t3 = t2 * t;

            float f1 = 2 * t3 - 3 * t2 + 1;
            float f2 = -2 * t3 + 3 * t2;
            float f3 = t3 - 2 * t2 + t;
            float f4 = t3 - t2;

            Vector3 point = p2 * f1 + tan1 * f3;
            point = point + p3 * f2 + tan2 * f4;

            return point;
        }

        #endregion

        #region IEquatable Implementation

        /// <summary>
        /// 
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override bool Equals(object obj)
        {
            return obj is Vector3 && this == (Vector3)obj;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public bool Equals(Vector3 other)
        {
            return (this == other);
        }

        #endregion

        #region Hash Code

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            return (int)((X + Y + Z) % Int32.MaxValue);
        }

        #endregion
    }
}
